import json
import os
import re
import time

import hypercorn.asyncio
import jwt
import requests
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
from flask import Flask, request, jsonify
from flask_cors import CORS
from hypercorn.config import Config

import my_models
from my_models import client, config, Datas, base_path

app = Flask(__name__)
CORS(app)

SECRET_KEY = "cqu_secret_key"
TOKEN_EXPIRATION_TIME = config["token_expiration_time"]

# 读取私钥
f = open(os.path.join(base_path, "private_key.pem"), "rb")
private_key = serialization.load_pem_private_key(f.read(), password=None)
f.close()

# 存储用户的token（用一个字典来模拟redis）
# key: token， value：{openid: openid, device_id: device_id, exp: exp失效时间}
user_device_map = {}
exp_time = 3600  # token失效时间

# 微信的 appid 和 appsecret
APPID = my_models.config["app_id"]
APPSECRET = my_models.config["app_secret"]
if not APPID or not APPSECRET:
    raise Exception("Please set appid and appsecret in config.json")


@app.route("/", methods=["GET"])
def index():
    return "Hello, World!"


async def verify_token_post(request):
    try:
        authorization = request.headers.get("Authorization")
        if not authorization:
            return {"error": "Authorization header missing"}
        token = authorization.split(" ")[1]
        payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
        # 接收并解析客户端发送的加密数据（十六进制格式）
        encrypted_data_hex = request.json["data"]
        encrypted_data = bytes.fromhex(encrypted_data_hex)  # 将十六进制转换为字节流格式
        # 使用私钥解密数据
        decrypted_data = private_key.decrypt(
            encrypted_data,
            padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None)
        ).decode()
        # 将解密后的 JSON 字符串转换为字典
        original_data = json.loads(decrypted_data)
        original_data["sub"] = payload["sub"]
        return original_data
    except jwt.ExpiredSignatureError:
        return {"error": "Token expired"}
    except jwt.InvalidTokenError:
        return {"error": "Invalid token"}
    except:
        return {"error": "Token unknown error"}


@app.route("/data", methods=["POST"])
async def add_data():
    # 验证令牌并解密数据
    original_data = await verify_token_post(request)
    if "error" in original_data:
        return jsonify(original_data), 401
    # 解析数据
    try:
        data = Datas(
            time=original_data["time"],
            h=original_data["h"],
            m=original_data["m"],
            sbp=original_data["sbp"],
            dbp=original_data["dbp"],
            device_id=original_data["id"]
        )
    except Exception as e:
        return jsonify({"error": "Bad request" + str(e)}), 400
    # 将数据插入数据库
    try:
        client.blood_pressure.datas.insert_one(data.model_dump())
        return jsonify({"message": "Access granted", "user": original_data["sub"]}), 200
    except:
        return jsonify({"error": "Database error"}), 500


@app.route("/get_access_token", methods=["POST"])
async def get_access_token():
    ticket = None
    try:
        ticket = request.json.get("ticket")
    finally:
        if not ticket:
            return jsonify({"error": "Invalid request"}), 400
    # 从数据库中获取密钥
    tickets = client.blood_pressure.tickets.find_one({"ticket": ticket})
    if tickets:
        username = tickets["device_id"]
        payload = {"sub": username, "iat": time.time(), "exp": time.time() + TOKEN_EXPIRATION_TIME}
        token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
        return jsonify({"access_token": token}), 200
    else:
        return jsonify({"error": "Invalid ticket"}), 401


# 获取 OpenID
@app.route('/login', methods=['POST'])
async def get_openid():
    code = request.json.get('code')
    if not code:
        return jsonify({'error': 'Code is required'}), 400
    # 获取 openid 和 session_key
    url = f'https://api.weixin.qq.com/sns/jscode2session?appid={APPID}&secret={APPSECRET}&js_code={code}&grant_type=authorization_code'
    response = requests.get(url)
    data = response.json()
    print(data)
    openid = data.get('openid')
    if not openid:
        return jsonify({'error': 'Failed to get OpenID'}), 400
    # 搜索绑定设备
    device_id = my_models.search_device_id_by_openid(openid)
    # Step3中已经使用了jwt，此处仅使用简单方式生成token，并使用一个字典存储
    # 若有兴趣，可以使用redis等存储token，并继续以jwt生成
    exp = time.time() + exp_time
    d = {'openid': openid, 'device_id': device_id, 'exp': exp}
    token = jwt.encode(d, SECRET_KEY, algorithm='HS256')
    user_device_map[token] = d
    d['token'] = token
    return jsonify(d), 200


# 绑定设备码
@app.route('/bind_device', methods=['POST'])
async def bind_device_id():
    device_id = request.json.get('device_id')
    if not device_id:
        return jsonify({"success": False, "message": "设备码不能为空"}), 400
    token = request.headers.get('Authorization')
    # Step3中已经使用了jwt，此处仅简单使用一个字典存储
    if token not in user_device_map:
        return jsonify({'error': 'Invalid token'}), 401
    exp = user_device_map[token].get('exp')
    if time.time() > exp:
        return jsonify({'error': 'Token expired'}), 401
    openid = user_device_map[token].get('openid')
    my_models.bind_device(openid, device_id)
    return jsonify({"success": True}), 200


# 搜索指定日期全部数据
@app.route('/search_data/<date>', methods=['GET'])
async def search_data(date):
    # 验证Date为yyyy-mm-dd格式
    if not re.match(r'\d{4}-\d{2}-\d{2}', date):
        return jsonify({'error': 'Invalid date'}), 400
    token = request.headers.get('Authorization')
    if token not in user_device_map:
        return jsonify({'error': 'Invalid token'}), 401
    exp = user_device_map[token].get('exp')
    if time.time() > exp:
        return jsonify({'error': 'Token expired'}), 401
    device_id = user_device_map[token].get('device_id')
    data = my_models.search_data_by_device_and_date(device_id, str(date))
    return jsonify({"list": data}), 200


if __name__ == "__main__":
    config = Config()
    config.bind = ["0.0.0.0:5000"]
    config.debug = True  # 开启调试模式
    hypercorn.asyncio.serve(app, config)
